#include <stdio.h>
#include <stdlib.h>
#include "lib/rngs.h"
#include "lib/rvgs.h"
#include "main.h"

#define START         0.0              /* initial time                   */

double arrival; /* the current arrival */
long Size; /*size of the system*/
double STOP;
int Run;

typedef struct {
	double arrival; /* next arrival time                   */
	double completion; /* next completion time                */
} t;

typedef struct {
	double current; /* current time                        */
	double next; /* next (most imminent) event time     */
	double last; /* last arrival time */
} time;

typedef struct {
	double node; /* time integrated number in the node  */
	double queue; /* time integrated number in the queue */
	double service; /* time integrated number in service   */
} area;

int main(int argc, char **argv) {

	/*consistency check*/
	if (argc != 4){
		fprintf(stderr, "Errore. Parametri di inizializzazione mancanti.\n");
		exit(EXIT_FAILURE);
	}
	if((Size = atol(argv[1])) == -1 || (STOP = atof(argv[2])) == -1 || (Run = atoi(argv[3])) == -1){
		fprintf(stderr, "Errore. Parametri di inizializzazione errati.\n");
		exit(EXIT_FAILURE);
	}

	/*data structure initialization*/
	data_files* datafiles = malloc(sizeof(data_files));
	collected_data* data = malloc(sizeof(collected_data));
	openFiles(datafiles);

	arrival = START;



	/* terminal (close the door) time */
	double Never = 0;
	Never = (100.0 * STOP);

	/* must be much larger than STOP  */
	area areaf;
	area areadb;
	t dbEvents;
	t FEvents;
	time clock;
	int i;

	for (i = 0; i < Run; i++) {
		PlantSeeds(-1);
		arrival = START;
		areaf.node = 0.0;
		areaf.queue = 0.0;
		areaf.service = 0.0;
		areadb.node = 0.0;
		areadb.queue = 0.0;
		areadb.service = 0.0;
		dbEvents.arrival = Never;
		dbEvents.completion = Never;

		FEvents.completion = Never;
		clock.current = START;
		clock.last = 0.0;
		clock.next = 0.0;
		FEvents.arrival = GetFrontArrival();

		data->rejected = 0;/*number of rejected jobs*/
		long feedbacked = 0;/* number of jobs on feedback */
		long indexF = 0; /* used to count departed jobs         */
		long numberF = 0; /* number in the node                  */
		data->indexDB = 0; /* used to count departed jobs         */
		long numberDB = 0; /* number in the node                  */
		long numberTotal = numberDB + numberF; /* total number of jobs in the system*/

		while ((FEvents.arrival < STOP) || (numberF + numberDB > 0)
				|| (dbEvents.arrival < Never)) {
			numberTotal = numberDB + numberF;
			clock.next = Min(FEvents.arrival, FEvents.completion); /* next event time   */
			clock.next = Min(clock.next, dbEvents.arrival); /* next event time   */
			clock.next = Min(clock.next, dbEvents.completion);
			if (numberF > 0) { /* update integrals  */
				areaf.node += (clock.next - clock.current) * numberF;
				areaf.queue += (clock.next - clock.current) * (numberF - 1);
				areaf.service += (clock.next - clock.current);
			}
			if (numberDB > 0) { /* update integrals  */
				areadb.node += (clock.next - clock.current) * numberDB;
				areadb.queue += (clock.next - clock.current) * (numberDB - 1);
				areadb.service += (clock.next - clock.current);
			}

			clock.current = clock.next; /* advance the clock */

			if (clock.current == FEvents.arrival) { /* process an arrival */
				if (FEvents.arrival > STOP) {

					FEvents.arrival = Never;

				} else {
					if (numberTotal < Size) {

						numberF++;
						if (numberF == 1) {

							FEvents.completion = clock.current
									+ GetFrontService();
						}
						FEvents.arrival = GetFrontArrival();

					} else {
						data->rejected++;
						FEvents.arrival = GetFrontArrival();
					}
				}
			}

			else if (clock.current == FEvents.completion) { /* process a completion */
				indexF++;
				numberF--;
				if (numberF > 0)
					FEvents.completion = clock.current + GetFrontService();
				else
					FEvents.completion = Never;
				dbEvents.arrival = clock.current;
			} else if (clock.current == dbEvents.arrival) {
				numberDB++;
				dbEvents.arrival = Never;
				if (numberDB == 1)
					dbEvents.completion = clock.current + GetDBService();

			} else {
				data->indexDB++;
				numberDB--;

				if (feedback(0.6)) {
					feedbacked++;
					numberF++;
					data->indexDB--;
					indexF--;

					if (numberF == 1)
						FEvents.completion = clock.current + GetFrontService();

				}
				if (numberDB > 0) {
					dbEvents.completion = clock.current + GetDBService();
				} else
					dbEvents.completion = Never;

			}
		}

		data->ResFTimeAvg = (double) areaf.node / (double) data->indexDB;
		data->ResDbTimeAvg = (double) areadb.node / (double) data->indexDB;
		data->RispTimeAvg = data->ResFTimeAvg + data->ResDbTimeAvg;
		data->DbPopAvg = ((double) areadb.node) / clock.current;
		data->FPopAvg = (double) areaf.node / clock.current;
		data->TroughputAvg = ((double) data->indexDB / clock.current);
		data->utilizationF = ((double) areaf.service / clock.current);
		data->utilizationDB = ((double) areadb.service / clock.current);

		printCollectedData(data);
		writeDataToFile(datafiles, data);

	}
	closeFiles(datafiles);
	exit(EXIT_SUCCESS);
}

void openFiles(data_files* files_struct) {
	files_struct->FrontAvgPop = fopen("frontavgpop", "w");
	files_struct->DBAvgPop = fopen("dbavgpop", "w");
	files_struct->FrontAvgUtil = fopen("frontavgutil", "w");
	files_struct->DBAvgUtil = fopen("dbavgutil", "w");
	files_struct->FrontAvgResTime = fopen("frontavgrestime", "w");
	files_struct->DBAvgResTime = fopen("dbavgrestime", "w");
	files_struct->SysAvgTHR = fopen("sysavgthr", "w");
	files_struct->SysAvgRespTime = fopen("sysavgresptime", "w");
	files_struct->CompletedJobs = fopen("completedjobs", "w");
	files_struct->DeniedJobs = fopen("deniedjobs", "w");
}

void closeFiles(data_files* files_struct) {
	fclose(files_struct->FrontAvgPop);
	fclose(files_struct->DBAvgPop);
	fclose(files_struct->FrontAvgUtil);
	fclose(files_struct->DBAvgUtil);
	fclose(files_struct->FrontAvgResTime);
	fclose(files_struct->DBAvgResTime);
	fclose(files_struct->SysAvgTHR);
	fclose(files_struct->SysAvgRespTime);
	fclose(files_struct->CompletedJobs);
	fclose(files_struct->DeniedJobs);
}

void writeDataToFile(data_files* files_struct, collected_data* data) {
	fprintf(files_struct->FrontAvgPop, "%f\n", data->FPopAvg);
	fprintf(files_struct->DBAvgPop, "%f\n", data->DbPopAvg);
	fprintf(files_struct->FrontAvgUtil, "%f\n", data->utilizationF);
	fprintf(files_struct->DBAvgUtil, "%f\n", data->utilizationDB);
	fprintf(files_struct->FrontAvgResTime, "%f\n", data->ResFTimeAvg);
	fprintf(files_struct->DBAvgResTime, "%f\n", data->ResDbTimeAvg);
	fprintf(files_struct->SysAvgTHR, "%f\n", data->TroughputAvg);
	fprintf(files_struct->SysAvgRespTime, "%f\n", data->RispTimeAvg);
	fprintf(files_struct->CompletedJobs, "%d\n", data->indexDB);
	fprintf(files_struct->DeniedJobs, "%d\n", data->rejected);
}

void printCollectedData(collected_data* data) {
	printf("tempo di risposta medio totale %f\n", data->RispTimeAvg);
	printf("tempo di risposta medio front %f\n", data->ResFTimeAvg);
	printf("tempo di risposta medio db %f\n", data->ResDbTimeAvg);
	printf("popolazione media db %f\n", data->DbPopAvg);
	printf("popolazione media front %f\n", data->FPopAvg);
	printf("throughput %f\n", data->TroughputAvg);
	printf("utilizzazione front node %f\n", data->utilizationF);
	printf("utilizzazione DB node %f\n", data->utilizationDB);
	printf("rejected %d\n", data->rejected);
	printf("completed %d\n", data->indexDB);
}

double Min(double a, double b) {
	if (a < b)
		return (a);
	else
		return (b);
}

/*Ritorna il tempo di arrivo al front server*/
double GetFrontArrival() {
	SelectStream(0);
	arrival += Exponential(0.4);
	return (arrival);
}

/*Ritorna il tempo di servizio del Front Server*/
double GetFrontService() {
	SelectStream(1);
	return (Exponential(0.2));
}

/*Ritorna il tempo di servizio al DB*/
double GetDBService() {
	SelectStream(2);
	return (Exponential(0.05));
}

/*ritorna 1 se feedback con probabilità p, altrimenti 0*/
int feedback(double p) {
	SelectStream(4);
	double p1 = 0;
	p1 = Uniform(0.0, 1.0);
	if (p1 <= p)
		return 1;
	return 0;
}

